﻿/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using SlimDX;
using SlimDX.Direct3D11;

namespace JBBRXG11
{
    public class BlendState : IDisposable
    {
        /// <summary>
        /// A built-in state object with settings for additive blend that is adding the
        /// destination data to the source data without using alpha.
        /// </summary>
        public static readonly BlendState Additive;
        /// <summary>
        /// A built-in state object with settings for alpha blend that is blending the
        /// source and destination data using alpha.
        /// </summary>
        public static readonly BlendState AlphaBlend;
        /// <summary>
        /// A built-in state object with settings for blending with non-premultipled
        /// alpha that is blending source and destination data by using alpha while assuming
        /// the color data contains no alpha information.
        /// </summary>
        public static readonly BlendState NonPremultiplied;
        /// <summary>
        /// A built-in state object with settings for opaque blend that is overwriting
        /// the source with the destination data.
        /// </summary>
        public static readonly BlendState Opaque;

        static BlendState()
        {
            Additive = new BlendState();
            Additive.ColorDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.One;
            Additive.AlphaDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.One;
            Additive.ColorSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.SourceAlpha;
            Additive.AlphaSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.SourceAlpha;

            AlphaBlend = new BlendState();  // Assumes premultiplication of textures
            AlphaBlend.ColorDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.InverseSourceAlpha;
            AlphaBlend.AlphaDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.InverseSourceAlpha;
            AlphaBlend.ColorSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.One;
            AlphaBlend.AlphaSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.One;

            NonPremultiplied = new BlendState();
            NonPremultiplied.ColorDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.InverseSourceAlpha;
            NonPremultiplied.AlphaDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.InverseSourceAlpha;
            NonPremultiplied.ColorSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.SourceAlpha;
            NonPremultiplied.AlphaSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.SourceAlpha;

            Opaque = new BlendState();
        }

        internal BlendStateDescription bsdesc;  // Accessible for SpriteBatch - temporary fix
        SlimDX.Direct3D11.BlendState dxblendstate;
        uint multisamplemask;
        Color4 blendfactor;

        internal SlimDX.Direct3D11.BlendState Get_DX_BlendState(Device dx_dev)
        {
            if (dxblendstate == null)
                dxblendstate = SlimDX.Direct3D11.BlendState.FromDescription(dx_dev, bsdesc);
            return dxblendstate;
        }

        private void modified()
        {
            if (dxblendstate != null)
            {
                dxblendstate.Dispose();
                dxblendstate = null;
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Constructors ////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public BlendState()
        {
            // XNA 4 doesn't seem to support multiple targets or blendenable - so always set enable true
            bsdesc = new BlendStateDescription();  // Default is Additive blending
            bsdesc.AlphaToCoverageEnable = false;  // TODO: Find out what this means
            for (int t = 0; t < 8; t++)
            {
                bsdesc.RenderTargets[t].BlendEnable = true;
                bsdesc.RenderTargets[t].BlendOperation = BlendOperation.Add;
                bsdesc.RenderTargets[t].BlendOperationAlpha = BlendOperation.Add;
                bsdesc.RenderTargets[t].DestinationBlend = BlendOption.Zero;
                bsdesc.RenderTargets[t].DestinationBlendAlpha = BlendOption.Zero;
                bsdesc.RenderTargets[t].RenderTargetWriteMask = (t == 0 ? ColorWriteMaskFlags.All : ColorWriteMaskFlags.None);
                bsdesc.RenderTargets[t].SourceBlend = BlendOption.One;
                bsdesc.RenderTargets[t].SourceBlendAlpha = BlendOption.One;
            }
            dxblendstate = null;
            multisamplemask = 0xffffffff;
            blendfactor = new Color4(1, 1, 1, 1);
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(this);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Properties //////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Gets or sets a bitmask which defines which samples can be written during
        /// multisampling. The default is 0xffffffff.
        /// </summary>
        public int MultiSampleMask
        {
            get { return (int)multisamplemask; }
            set { multisamplemask = (uint)value; modified(); }
        }

        /// <summary>
        /// Gets or sets the four-component (RGBA) blend factor for alpha blending.
        /// </summary>
        public Microsoft.Xna.Framework.Color BlendFactor
        {
            get { return EnumConversions.convert(blendfactor); }
            set { blendfactor = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets which color channels (RGBA) are enabled for writing during
        /// color blending. The default value is ColorWriteChannels.None.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.ColorWriteChannels ColorWriteChannels3
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[3].RenderTargetWriteMask); }
            set { bsdesc.RenderTargets[3].RenderTargetWriteMask = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets which color channels (RGBA) are enabled for writing during
        /// color blending. The default value is ColorWriteChannels.None.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.ColorWriteChannels ColorWriteChannels2
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[2].RenderTargetWriteMask); }
            set { bsdesc.RenderTargets[2].RenderTargetWriteMask = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets which color channels (RGBA) are enabled for writing during
        /// color blending. The default value is ColorWriteChannels.None.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.ColorWriteChannels ColorWriteChannels1
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[1].RenderTargetWriteMask); }
            set { bsdesc.RenderTargets[1].RenderTargetWriteMask = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets which color channels (RGBA) are enabled for writing during
        /// color blending. The default value is ColorWriteChannels.None.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.ColorWriteChannels ColorWriteChannels
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].RenderTargetWriteMask); }
            set { bsdesc.RenderTargets[0].RenderTargetWriteMask = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the arithmetic operation when blending alpha values. The
        /// default is BlendFunction.Add.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.BlendFunction AlphaBlendFunction
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].BlendOperationAlpha); }
            set { bsdesc.RenderTargets[0].BlendOperationAlpha = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the blend factor for the destination alpha, which is the
        /// percentage of the destination alpha included in the blended result. The
        /// default is Blend.One.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.Blend AlphaDestinationBlend
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].DestinationBlendAlpha); }
            set { bsdesc.RenderTargets[0].DestinationBlendAlpha = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the alpha blend factor. The default is Blend.One.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.Blend AlphaSourceBlend
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].SourceBlendAlpha); }
            set { bsdesc.RenderTargets[0].SourceBlendAlpha = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the arithmetic operation when blending color values. The
        /// default is BlendFunction.Add.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.BlendFunction ColorBlendFunction
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].BlendOperation); }
            set { bsdesc.RenderTargets[0].BlendOperation = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the blend factor for the destination color. The default is
        /// Blend.One.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.Blend ColorDestinationBlend
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].DestinationBlend); }
            set { bsdesc.RenderTargets[0].DestinationBlend = EnumConversions.convert(value); modified(); }
        }

        /// <summary>
        /// Gets or sets the blend factor for the source color. The default is
        /// Blend.One.
        /// </summary>
        public Microsoft.Xna.Framework.Graphics.Blend ColorSourceBlend
        {
            get { return EnumConversions.convert(bsdesc.RenderTargets[0].SourceBlend); }
            set { bsdesc.RenderTargets[0].SourceBlend = EnumConversions.convert(value); modified(); }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////// Methods /////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Immediately releases the unmanaged resources used by this object.
        /// </summary>
        public void Dispose()
        {
            if (dxblendstate != null)
            {
                dxblendstate.Dispose();
            }
        }
    }
}
